@file:JvmMultifileClass
@file:JvmName("StringUtils")

package hos.kotlin

import java.net.URLDecoder
import java.net.URLEncoder
import java.text.SimpleDateFormat
import java.util.Arrays
import java.util.Base64
import java.util.Calendar
import java.util.GregorianCalendar

/**
 * <p>Title: StringExt </p>
 * <p>Description:  </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @date : 2024-07-11 13:39
 * @version : 1.0
 */

fun String?.isNull(): Boolean {
    return this == null || (isEmpty() || "null" === this
            || "NULL" === this || "Null" === this)
}

fun String?.isNotNull(): Boolean {
    return !isNull()
}

@JvmOverloads
fun String?.getOrNull(value: String? = null, convert: (String.() -> String)? = null): String? {
    if (this == null || isNull()) {
        return value;
    }
    return convert?.invoke(this) ?: this;
}

@JvmOverloads
fun String?.opt(value: String, convert: (String.() -> String)? = null): String {
    return getOrNull(value, convert) ?: value
}

@JvmOverloads
fun String?.empty(value: String = "", convert: (String.() -> String)? = null): String {
    return opt(value, convert)
}

@JvmOverloads
fun String?.like(value: String = "%%"): String {
    return empty(value) {
        "%$this%"
    }
}

fun String?.isInt(): Boolean {
    return this != null && this.toIntOrNull() != null
}

fun String?.isDouble(): Boolean {
    return this != null && this.toDoubleOrNull() != null
}

fun String?.isBool(): Boolean {
    return this != null && this.toBooleanStrictOrNull() != null
}

@JvmOverloads
fun CharSequence?.includes(other: CharSequence, ignoreCase: Boolean = false): Boolean {
    return this != null && contains(other, ignoreCase)
}

fun String?.subStr(start: Int, count: Int): String {
    if (this == null || isNull() || start < 0 || count < 0 || start + count > length) {
        return ""
    }
    return substring(start, start + count)
}

fun String?.fontLength(): Int {
    val target: String = empty().replace("[^\\x00-\\xff]", "**")
    var strLength = 0
    val chinese = "[\u0391-\uFFE5]".toRegex()
    /* 获取字段值的长度，如果含中文字符，则每个中文字符长度为2，否则为1 */
    for (i in target.indices) {
        /* 从字符串中获取一个字符 */
        val temp = target.substring(i, i + 1)
        strLength += if (temp.matches(chinese)) {
            /* 中文字符长度为2 */
            2
        } else {
            /* 其他字符长度为1 */
            1
        }
    }
    return strLength
}

@JvmOverloads
fun String?.asBool(value: Boolean = false): Boolean {
    if (this == null || this.isNull()) {
        return value
    }
    return try {
        toBooleanStrict()
    } catch (e: Exception) {
        return when (this) {
            "true", "是", "有", "正确", "1", "1.0" -> true
            "false", "否", "无", "错误", "0", "0.0" -> false
            else -> value
        }
    }
}

fun String?.asNum(value: Number): Number {
    if (this == null || this.isNull()) {
        return value
    }
    return try {
        toDouble()
    } catch (e: Exception) {
        when (this) {
            "true", "是", "有", "正确", "1", "1.0" -> 1
            "false", "否", "无", "错误", "0", "0.0" -> 0
            else -> value
        }
    }
}

fun String?.asInt(value: Int): Int {
    if (this == null || this.isNull()) {
        return value
    }
    return try {
        toInt()
    } catch (e: Exception) {
        when (this) {
            "true", "是", "有", "正确", "1", "1.0" -> 1
            "false", "否", "无", "错误", "0", "0.0" -> 0
            else -> value
        }
    }
}


fun String?.asDouble(value: Double): Double {
    if (this == null || this.isNull()) {
        return value
    }
    return try {
        toDouble()
    } catch (e: Exception) {
        when (this) {
            "true", "是", "有", "正确", "1", "1.0" -> 1.0
            "false", "否", "无", "错误", "0", "0.0" -> 0.0
            else -> value
        }
    }
}

fun String?.left(len: Int): String {
    if (this == null || isNull() || len < 0) {
        return ""
    }
    if (length <= len) {
        return this
    }
    return substring(0, len)
}


fun String?.right(len: Int): String {
    if (this == null || isNull() || len < 0) {
        return ""
    }
    if (length <= len) {
        return this
    }
    return substring(length - len)
}

fun Char?.repeat(repeat: Int): String {
    if (this == null || repeat <= 0) {
        return ""
    } else {
        val buf = CharArray(repeat)
        Arrays.fill(buf, this)
        return String(buf)
    }
}

fun String?.leftPad(size: Int, padChar: Char): String? {
    if (this == null || isNull()) {
        return ""
    }
    val pads = size - length
    return if (pads <= 0) {
        this
    } else {
        if (pads > 8192) leftPad(
            size,
            padChar.toString()
        ) else padChar.repeat(pads) + this
    }
}

@JvmOverloads
fun String?.leftPad(size: Int, padding: String = " "): String? {
    if (this == null || isNull()) {
        val padChars: CharArray = padding.toCharArray()
        val paddingArrays = CharArray(size)
        val padLen = padding.length
        for (i in 0 until size) {
            paddingArrays[i] = padChars[i % padLen]
        }
        return String(paddingArrays)
    }
    val pads = size - length
    if (pads <= 0) {
        return this
    }
    val padLen = padding.length
    if (padLen == 1 && pads <= 8192) {
        return leftPad(size, padding[0])
    } else if (pads == padLen) {
        return padding + this
    } else if (pads < padLen) {
        return padding.substring(0, pads) + this
    } else {
        val paddingArrays = CharArray(pads)
        val padChars = padding.toCharArray()
        for (i in 0 until pads) {
            paddingArrays[i] = padChars[i % padLen]
        }
        return (String(paddingArrays)) + this
    }
}

fun String?.rightPad(size: Int, padChar: Char): String {
    if (this == null || isNull()) {
        return ""
    }
    val pads = size - length
    if (pads <= 0) {
        return this
    }
    return if (pads > 8192) this.rightPad(
        size,
        padChar.toString()
    ) else this + padChar.repeat(pads)
}

@JvmOverloads
fun String?.rightPad(size: Int, padding: String = " "): String {
    if (this == null || isNull()) {
        val padChars: CharArray = padding.toCharArray()
        val paddingArrays = CharArray(size)
        val padLen = padding.length
        for (i in 0 until size) {
            paddingArrays[i] = padChars[i % padLen]
        }
        return String(paddingArrays)
    }
    val pads = size - length
    if (pads <= 0) {
        return this
    }
    val padLen = padding.length
    if (padLen == 1 && pads <= 8192) {
        return rightPad(size, padding[0])
    } else if (pads == padLen) {
        return this + padding
    } else if (pads < padLen) {
        return this + padding.substring(0, pads)
    }
    val padChars: CharArray = padding.toCharArray()
    val paddingArrays = CharArray(pads)
    for (i in 0 until pads) {
        paddingArrays[i] = padChars[i % padLen]
    }
    return this + String(paddingArrays)
}


fun String?.encodeBase64(): String? {
    if (this == null || isNull()) {
        return null
    }
    val bytes = toByteArray()
    return Base64.getEncoder().encodeToString(bytes)
}

fun String?.encodeBase64ByteArray(): ByteArray? {
    if (this == null || isNull()) {
        return null
    }
    val bytes = toByteArray()
    return Base64.getEncoder().encode(bytes)
}

fun String?.decodeBase64(): String? {
    if (this == null || isNull()) {
        return null
    }
    val bytes = toByteArray()
    return Base64.getDecoder().decode(bytes).decodeToString()
}

fun String?.decodeBase64ByteArray(): ByteArray? {
    if (this == null || isNull()) {
        return null
    }
    val bytes = toByteArray()
    return Base64.getDecoder().decode(bytes)
}

@JvmOverloads
fun String?.encodeUrl(charsetName: String = "UTF-8"): String? {
    if (this == null || isNull()) {
        return null
    }
    try {
        return URLEncoder.encode(this, charsetName)
    } catch (e: Exception) {
        e.printStackTrace()
        return null
    }
}

@JvmOverloads
fun String?.decodeUrl(charsetName: String = "UTF-8"): String? {
    if (this == null || isNull()) {
        return null
    }
    try {
        return URLDecoder.decode(this, charsetName)
    } catch (e: Exception) {
        e.printStackTrace()
        return null
    }
}

@JvmOverloads
fun String?.generateMd5(salt: ByteArray? = null): ByteArray? {
    return this?.toByteArray().generateMd5(salt)
}

@JvmOverloads
fun String?.generateMd5String(salt: ByteArray? = null): String? {
    return this?.toByteArray().generateMd5String(salt)
}

fun String?.generateSha1(): ByteArray? {
    return this?.toByteArray().encryptSHA1()
}

fun String?.generateSha1String(): String? {
    return this?.toByteArray().encryptSHA1String()
}

fun String?.generateSha256(): ByteArray? {
    return this?.toByteArray().encryptSHA256()
}

fun String?.generateSha256String(): String? {
    return this?.toByteArray().encryptSHA256String()
}

fun String?.generateSha512(): ByteArray? {
    return this?.toByteArray().encryptSHA512()
}

fun String?.generateSha512String(): String? {
    return this?.toByteArray().encryptSHA512String()
}

fun String?.generateSha224(): ByteArray? {
    return this?.toByteArray().encryptSHA224()
}

fun String?.generateSha224String(): String? {
    return this?.toByteArray().encryptSHA224String()
}

fun String?.generateSha384(): ByteArray? {
    return this?.toByteArray().encryptSHA384()
}

fun String?.generateSha384String(): String? {
    return this?.toByteArray().encryptSHA384String()
}

fun String?.hexString(isUpperCase: Boolean = true): String? {
    return this?.toByteArray().hexString(isUpperCase)
}

fun String?.time(value: String = ""): String {
    return empty(value) {
        var time = this;
        if (contains("T") ||
            contains("+") ||
            contains(".")
        ) {
            if (contains("T")) {
                time = time.replace("T", " ");
            }
            if (contains(".")) {
                time = time.substring(0, time.lastIndexOf("."));
            }
            if (contains("+")) {
                time = time.substring(0, time.lastIndexOf("+"));
            }
            return@empty time
        }
        return@empty time
    }
}

@Suppress("SimpleDateFormat")
fun String?.timeLong(from: String = TimeFormat.full, value: Long = 0): Long {
    val time = time()
    if (time.isNull()) {
        return value
    }
    return SimpleDateFormat(from).parse(time).time
}

@Suppress("SimpleDateFormat")
fun String?.calendar(
    from: String = TimeFormat.full,
    value: Calendar = GregorianCalendar()
): Calendar {
    val time = time()
    if (time.isNull()) {
        return value
    }
    val instance = Calendar.getInstance()
    instance.timeInMillis = time.timeLong(from, value.timeInMillis)
    return instance
}

fun String?.convert(from: String = TimeFormat.full, to: String = TimeFormat.full): String {
    return calendar(from).string(to)
}

fun Long?.calendar(value: Long? = null): Calendar? {
    val instance = Calendar.getInstance()
    if (this == null) {
        if (value != null) {
            instance.timeInMillis = value
            return instance
        }
        return null
    }
    instance.timeInMillis = this
    return instance
}

fun Long?.time(to: String = TimeFormat.full, value: String = ""): String {
    return calendar()?.string(to) ?: value
}


fun String?.isValidLatitude(zh: Boolean = true): Boolean {
    if (this == null || this.isNull()) {
        return false
    }
    if (startsWith("0") || startsWith("O") || startsWith("o")
        || this == "0.0" || this == "0.00"
    ) {
        // 纬度格式不正确
        return false;
    }
    val regex = """^(-?[0-9]|[1-8][0-9]|90)(\\.\\d+)?${'$'}""".toRegex()
    if (!zh) {
        return this.matches(regex)
    }
    if (!this.matches(regex)) {
        return false
    }
    val value = asDouble(0.0)
    // 经度在国内，正确
    return value in 3.86..53.56;
}

fun String?.isValidLongitude(zh: Boolean = true): Boolean {
    if (this == null || this.isNull()) {
        return false
    }
    if (startsWith("0") || startsWith("O") || startsWith("o")
        || this == "0.0" || this == "0.00"
    ) {
        // 纬度格式不正确
        return false;
    }
    val regex = """^(-?((1[0-7][0-9])|([0-9]{1,2}))(\.\d+)?|180(\.0+)?)${'$'}""".toRegex()
    if (!zh) {
        return this.matches(regex)
    }
    if (!this.matches(regex)) {
        return false
    }
    val value = asDouble(0.0)
    // 经度在国内，正确
    return value in 73.66..135.05;
}

fun String?.isSocialNumber(): Boolean {
    if (this == null || this.isNull()) {
        return false
    }
    if (length != 18) {
        return false
    }
    // 判断第18位是否为汉字
    val regex = "^[\\u4e00-\\u9fa5]+${'$'}".toRegex()
    return !regex.matches(this)
}

fun String?.checkPassword(
// 包含大写字母
    upperCase: Boolean = false,
    lowerCase: Boolean = false, // 包含小写字母
    letter: Boolean = false, // 包含字母
    digit: Boolean = false, // 包含数字
    special: Boolean = false, // 包含特殊字符
    minLength: Int = 8, // 最小长度
    maxLength: Int = 30,
    specialCharSet: MutableSet<Char> = mutableSetOf<Char>(
        '~', '`', '!', '@', '#', '$', '%', '^',
        '&', '*', '(', ')', '-', '_', '+', '=',
        '{', '[', '}', ']', '|', '\\', ':', ';',
        '"', '\'', '<', ',', '>', '.', '?', '/',
    ),// 特殊字符集合
): Boolean {
    if (this == null || this.isNull()) {
        return false
    }
    return PasswordChecker(
        upperCase, lowerCase,
        letter, digit, special,
        minLength, maxLength, specialCharSet
    ).check(this)
}